/*

Gamebots UT Copyright (c) 2002, Andrew N. Marshal, Gal Kaminka
Gamebots UT 2003/2004 port Copyright (c) 2005
       Joe Manojlovich, Tim Garwood, Jessica Bayliss
Gamebots Pogamut derivation Copyright (c) 2007, Michal Bida
	- derived mainly from 2002 Marshal, Kaminka version

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software must also be in compliance with the Epic Games Inc. license for mods which states the following: "Your mods must be distributed solely for free, period. Neither you, nor any other person or party, may sell them to anyone, commercially exploit them in any way, or charge anyone for receiving or using them without prior written consent of Epic Games Inc. You may exchange them at no charge among other end-users and distribute them to others over the Internet, on magazine cover disks, or otherwise for free." Please see http://www.epicgames.com/ut2k4_eula.html for more information.

*/


class GBClientClass extends TcpLink
	config(BotAPI);

// the main variables where we have incoming messages stored
var string ReceivedData;
var string ReceivedArgs[9];
var string ReceivedVals[9];

//used to store info inside function where local wont work
//set property text dosent seem to work with a local
var actor tempActor;

// set true for verbose debug logs
var config bool bDebug;

// set true for iterative mode
var config bool bIterative;

// enables disables cheating - invulnerability, spawning items for bots
var config bool bAllowCheats;

// if control server or bots can or cannot pause the game
var config bool bAllowPause;

var config bool bNewProtocol;

//For bot exporting (ExportPlayers())
var bool bExportHumanPlayers;
var bool bExportRemoteBots;
var bool bExportUnrealBots;

var GBClientClass Next; //create list of all classes

// delimeters for strings sent to clients. set to match those of the server
// as = identifies the start of an attribute
var string as;
// ae = attribute end
var string ae;
// ib = item break
var string ib;

//Here is where we handle incoming commands
/* Commands expected to look like:
runto {Argument value} {Arg value}...
Currently hard coded to take no more than 9 args
Command type and arguments can be
any length, but first space terminates the name. Values can
have spaces or any other kind of character.
*/
function ReceivedLine(string S)
{
	local string cmdType, argBody, rem;
	local int endloc, wordsplit, attrNum;

	if(bDebug)
		log(S);

	wordsplit = InStr(S,ib);
	if( wordsplit == -1)
		wordsplit = Len(S);

	cmdType = left(S,wordsplit);
	rem = mid(S,InStr(S,as));

	attrNum = 0;
	// clear previously received attr/val pairs
	while(attrNum < 9)
	{
		if (ReceivedArgs[attrNum] == "")
			break;

		ReceivedArgs[attrNum] = "";
		ReceivedVals[attrNum] = "";

		attrNum++;
	}

	attrNum = 0;

	//iterate through attr/val pairs, storring them in the
	//parallel arrays ReceivedArgs and ReceivedVals
	while(attrNum < 9 && rem != "")
	{
		endloc = InStr(rem,ae);
		argBody = mid(rem,1,(endloc - 1));

		wordsplit = InStr(argBody,ib);
		ReceivedArgs[attrNum] = left(argBody,wordsplit);
		ReceivedVals[attrNum] = mid(argBody,(wordsplit + 1));

		rem = mid(rem,1); //advance
		rem = mid(rem,InStr(rem,as));
		attrNum++;
	}

	cmdType = Caps(cmdType);

	ProcessAction(cmdType);
}

//Recieve info - parse into lines and call RecievedLine
event ReceivedText( string Text )
{
	local int i;
	local string S;

    if(bDebug)
    	log("Recieved - "$Text);

	ReceivedData = ReceivedData $ Text;

	// remove a LF which arrived in a new packet
	// and thus didn't get cleaned up by the code below
	if(Left(ReceivedData, 1) == Chr(10))
		ReceivedData = Mid(ReceivedData, 1);
	i = InStr(ReceivedData, Chr(13));
	while(i != -1)
	{
		S = Left(ReceivedData, i);
		i++;
		// check for any LF following the CR.
		if(Mid(ReceivedData, i, 1) == Chr(10))
			i++;

		ReceivedData = Mid(ReceivedData, i);

		ReceivedLine(S);

		if(LinkState != STATE_Connected)
			return;

		i = InStr(ReceivedData, Chr(13));
	}
}

function ProcessAction(string cmdType)
{
	//Shouldnt be called, just for inheritance
	log("Error - we are in GBClientClass, ProcessAction() ");
}

// function for getting string value of input received attribute
function string GetArgVal(string argName)
{

	local int i;
	while (i < 9 && ReceivedArgs[i] != "")
	{
		if (ReceivedArgs[i] ~= argName)
			return ReceivedVals[i];
		i++;
	}

	return "";
}

// should use int's for locations rather than floats
// we don't need to be that precise
function ParseVector(out vector v, string vecName)
{
	local int i;
	local string rem;
	local string delim;

	delim = ib;

	rem = GetArgVal(vecName);
	if(rem != "")
	{
		if( InStr(rem,delim) == -1 )
			delim = ",";
		i = InStr(rem,delim);
		v.X = float(left(rem,i));
		rem = mid(rem,i+1);
		i = InStr(rem,delim);
		v.Y = float(left(rem,i));
		v.Z = float(mid(rem,i+1));
	}
	else
	{
		v.x = float( GetArgVal("x") );
		v.y = float( GetArgVal("y") );
		v.z = float( GetArgVal("z") );
	}
}

// function for parsing rotation
function ParseRot(out rotator rot, string rotName)
{
    local int i;
    local string rem;
    //local float y,p,r;
    local string delim;

    delim = ib;

	rem = GetArgVal(rotName);
	if(rem != "")
	{
        if( InStr(rem,delim) == -1 )
        	delim = ",";
        i = InStr(rem,delim);
        rot.Pitch = float(left(rem,i));
        rem = mid(rem,i+1);
        i = InStr(rem,delim);
        rot.Yaw = float(left(rem,i));
        rot.Roll = float(mid(rem,i+1));
	}
	else
	{
		rot.Pitch = float( GetArgVal("pitch") );
		rot.Yaw = float( GetArgVal("yaw") );
		rot.Roll = float( GetArgVal("roll") );
	}
}

//Send a line to the client
function SendLine(string Text, optional bool bNoCRLF)
{
    if(bDebug)
    	log("    Sending: "$Text);
	if(bNoCRLF)
		SendText(Text);
	else
		SendText(Text$Chr(13)$Chr(10));
}

//When we need to tell something to all
function GlobalSendLine(string Text, bool bNotifyAllControlServers, bool bNotifyAllBots, optional bool bNoCRLF)
{
	local GBClientClass G;

	if ((BotDeathMatch(Level.Game).theControlServer != none) && bNotifyAllControlServers)
	{
		for (G = BotDeathMatch(Level.Game).theControlServer.ChildList; G != None; G = G.Next )
		{
			G.SendLine(Text, bNoCRLF);
		}
	}

	if (bNotifyAllBots)
	{
		for (G = BotDeathMatch(Level.Game).theBotServer.ChildList; G != None; G = G.Next )
		{
		    if (BotConnection(G).theBot != none)
				G.SendLine(Text, bNoCRLF);
		}
	}
}

//Concat two strings togeather with our list bookends
function string MakeItem(string first, string second)
{
	return (as$first$ib$second$ae);
}

function SendGameInfo()
{
	local string gameInfoStr, levelName, PauseResult;
	local int i;

	gameInfoStr = BotDeathMatch(Level.Game).GetGameInfo();
	levelName = string(Level);
	i = InStr(Caps(levelName), ".LEVELINFO");

	if(i != -1)
		levelName = Left(levelName, i);

	if (Level.Pauser != None)
		PauseResult = "True";
	else
		PauseResult = "False";

	SendLine("NFO {Gametype " $ BotDeathMatch(Level.Game).GameClass $
		"} {GamePaused " $ PauseResult $
		"} {BotsPaused " $ Level.bPlayersOnly $
		"} {Level " $ levelName $
		"}" $ gameInfoStr);
}

//Exports all navpoints in a level also with their reachable graph
function ExportNavPoints()
{
	local string message, item, itemClass, flag;
	local NavigationPoint N, First;
	local int i,j,PathListLength;

	SendLine("SNAV");
	First = Level.NavigationPointList.NextNavigationPoint; //will be used in second loop, to eliminate already procesed objects

	for ( N=Level.NavigationPointList; N!=None; N=N.NextNavigationPoint )
	{
		item = "None";
		itemClass = "None";
		flag = "PathNode";
		if (N.IsA('InventorySpot'))
		{
			flag = "InventorySpot";

			// The items, which have pickup base DOES NOT have unique id.
			// We need to send unique id, so sending id of pickup base.
			if (InventorySpot(N).markedItem.PickUpBase == none)
				item = string(InventorySpot(N).markedItem);
			else
				item = string(InventorySpot(N).markedItem.PickUpBase);
			itemClass = string(InventorySpot(N).markedItem.class);
		}

		if (N.IsA('PlayerStart'))
		{
			flag = "PlayerStart";
		}


		if (N.IsA('AIMarker') && AIMarker(N).markedScript.IsA('UnrealScriptedSequence'))
		{

			flag = "AIMarker";

			message="INAV {Id " $ N $
				"} {Location " $ N.Location $
				"} {Flag " $ flag $
				"} {Item " $ item $
				"} {ItemClass " $ itemClass $
				"} {Rotation " $ UnrealScriptedSequence(AIMarker(N).markedScript).Rotation $
				"} {RoamingSpot " $ UnrealScriptedSequence(AIMarker(N).markedScript).bRoamingScript $
				"} {SnipingSpot " $ UnrealScriptedSequence(AIMarker(N).markedScript).bSniping $
				"} {PreferedWeapon " $ UnrealScriptedSequence(AIMarker(N).markedScript).WeaponPreference $
				"}";

		}
		else
		{

			message="INAV {Id " $ N $
				"} {Location " $ N.Location $
				"} {Flag " $ flag $
				"} {Item " $ item $
				"} {ItemClass " $ itemClass $
				"}";
		}

		i = 0;
		PathListLength = N.PathList.Length;
		while (i < PathListLength)
		{
			message = message$" {Neigh"$i$ " {Id " $ N.PathList[i].End $
				"} {Flags " $ N.PathList[i].reachFlags $
				"} {CollisionR " $ N.PathList[i].CollisionRadius $
				"} {CollisionH " $ N.PathList[i].CollisionHeight $
				"}";


  			if (N.PathList[i].End.IsA('JumpDest')) //we will add necessary information for jumps
			{
				message = message $" {ForceDoubleJump " $ JumpDest(N.PathList[i].End).bForceDoubleJump $
					"}" ;
				for (j = 0; j < JumpDest(N.PathList[i].End).NumUpstreamPaths; j++)
				{
					//Searching the record which refers to the path we are now exporting
					if (JumpDest(N.PathList[i].End).UpstreamPaths[j].Start == N)
					{
						message = message $ " {NeededJump " $ JumpDest(N.PathList[i].End).NeededJump[j] $
							"}";
					}
				}

			}

			message = message$"}"; //here we need to close the big attr. Neigh, that is left open because we did not know if we will add or not jumpDest information
			i++;
		}
		SendLine(message);
	}
	SendLine("ENAV");

}

/*

*/

function ExportInventory()
{
	local NavigationPoint N;
	local string uniqueId;

	SendLine("SINV");

	for ( N=Level.NavigationPointList; N!=None; N=N.NextNavigationPoint )
	{
		if (N.IsA('InventorySpot'))
		{
			// The items, which have pickup base DOES NOT have unique id.
			// We need to send unique id, so sending id of pickup base.
			if (InventorySpot(N).markedItem.PickUpBase == none)
				uniqueId = string(InventorySpot(N).markedItem);
			else
				uniqueId = string(InventorySpot(N).markedItem.PickUpBase);

			SendLine("IINV {Id " $ uniqueId $
				"} {Location " $ String(InventorySpot(N).markedItem.Location) $
				"} {Class " $ String(InventorySpot(N).markedItem.Class) $
				"}");
		}
	}
	SendLine("EINV");

}

//Exports all players in a level
function ExportPlayers()
{
	local Controller C;
	local string message;

	SendLine("SPLR");
	for(C = Level.ControllerList; C != None; C = C.NextController )
	{
		if ((bExportRemoteBots && C.IsA('RemoteBot')) || (bExportHumanPlayers && C.IsA('GBxPlayer')) || (bExportUnrealBots && C.IsA('GBxBot')))
		{
			message = "IPLR {Id "$C$C.PlayerReplicationInfo.PlayerID$
				"} {Name "$C.PlayerReplicationInfo.PlayerName $
				"}";

			if (C.Pawn != none)
				message = message$" {Location "$C.Pawn.Location$
					"} {Rotation "$C.Pawn.Rotation$"}";

			if (C.IsA('RemoteBot'))
				message = message$" {ManualSpawn " $ !RemoteBot(C).bAutoSpawn $
					"} {AutoTrace " $ RemoteBot(C).bAutoTrace $
					"} {Invulnerable " $ RemoteBot(C).bGodMode $
					"} {VisionTime " $ RemoteBot(C).myConnection.VisionTime $
					"} {ShowDebug " $ RemoteBot(C).bDebug $
					"} {ShowFocalPoint " $ RemoteBot(C).bShowFocalPoint $
					"} {DrawTraceLines " $ RemoteBot(C).bDrawTraceLines $
					"}";
			SendLine(message);
		}

    }
    SendLine("EPLR");

}

//Get amount of goodies we are provided when we pick various items
function int GetItemAmount(Pickup Pickup)
{
	local int amount;

	amount = 0;
	if (Pickup.IsA('TournamentHealth'))
	{
		amount = TournamentHealth(Pickup).HealingAmount;
	}
	else if (Pickup.IsA('ArmorPickup'))
	{
		amount = class<Armor>(Pickup.InventoryType).default.ArmorAbsorption;
	}
	else if (Pickup.IsA('AdrenalinePickup'))
	{
		amount = AdrenalinePickup(Pickup).AdrenalineAmount;
	}
	else if (Pickup.IsA('ShieldPickup'))
	{
		amount = ShieldPickup(Pickup).ShieldAmount;
	}
	else if (Pickup.IsA('Ammo'))
	{
		amount = Ammo(Pickup).AmmoAmount;
	}
	return amount;
}

function GetPlayers()
{
	local Controller C;
	local string message;
	local string Dead;

	for(C = Level.ControllerList; C != None; C = C.NextController )
	{
		if (C.IsA('RemoteBot') || C.IsA('GBxPlayer') || C.IsA('GBxBot'))
		{
			/*if (C.IsA('RemoteBot')
			{
				Type = "RemoteBot";
			}*/

			if (C.Pawn == None)
				Dead = "True";
			else
				Dead = "False";

			message = "PLR {Id "$C$C.PlayerReplicationInfo.PlayerID$
				"} {Name "$C.PlayerReplicationInfo.PlayerName$
				"} {PlayerType " $ C $
				"} {PlayerDead " $ Dead $
				"}";

			if (C.Pawn != none)
			{
				message = message$" {Location " $ C.Pawn.Location $
					"} {Rotation " $ C.Pawn.Rotation $"}";
			}

			SendLine(message);
		}

    }
}

defaultproperties
{
     bAllowPause=True
     bNewProtocol=True
     bExportHumanPlayers=True
     bExportRemoteBots=True
     bExportUnrealBots=True
     as="{"
     ae="}"
     ib=" "
}
